package com.jg.xmlSchema2JsonSchema;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.JSONWriter;
import com.jg.xmlSchema2JsonSchema.rules.*;
import com.jg.xmlSchema2JsonSchema.xsd.*;
import com.jg.xmlSchema2JsonSchema.xsd.util.XsdReader;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author czg
 */
public class XsdFileToJsonSchema {
    private Map<String, XsComplexType> complexTypeMap = new HashMap<>();
    private JSONObject jsonObject = new JSONObject();
    private ObjectRule rootObjectRule = new ObjectRule();
    private XsdReader xsdReader = new XsdReader();

    public XsdFileToJsonSchema(String xsdFileName) throws Exception {
        this(new File(xsdFileName));
    }

    public XsdFileToJsonSchema(File xsdFile) throws Exception {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db = dbf.newDocumentBuilder();
        Document document = db.parse(xsdFile);
        parseXsd(document);
    }

    public XsdFileToJsonSchema(InputStream xsdFile) throws Exception {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db = dbf.newDocumentBuilder();
        Document document = db.parse(xsdFile);
        parseXsd(document);
    }


    /**
     * 解析XSD，返回数据节点对象列表
     *
     * @param document
     * @throws Exception
     */
    private void parseXsd(Document document) {
        rootObjectRule.apply();
        Element rootElement = document.getDocumentElement();
        List<Element> complexTypeList = XmlUtil.getChildElements(rootElement,
                XMLConstants.XSD_DEFAULT_NAMESPACE + ":" + XMLConstants.XSD_COMPLEX_TYPE);
        for (Element element : complexTypeList) {
            XsComplexType xsComplexType = new XsComplexType(element);
            complexTypeMap.put(xsComplexType.getName(), xsComplexType);
        }
        xsdReader.setComplexTypeMap(complexTypeMap);
        List<Element> elementList = XmlUtil.getChildElements(rootElement,
                XMLConstants.XSD_DEFAULT_NAMESPACE + ":" + XMLConstants.XSD_ELEMENT);
        XsElement rootXsElement = new XsElement(elementList.get(0));
        rootObjectRule.put$schema();
        rootObjectRule.putType("object");
        addElement(rootObjectRule, rootXsElement);
    }


    /**
     * 添加元素
     *
     * @param parentObjectRule
     * @param xsElementList
     */
    private void addElements(ObjectRule parentObjectRule, List<XsElement> xsElementList) {
        if (CollectionUtil.isEmpty(xsElementList)) {
            return;
        }
        for (XsElement xsElement : xsElementList) {
            addElement(parentObjectRule, xsElement);
        }
    }

    /**
     * 添加属性项
     *
     * @param parentObjectRule
     * @param attributes
     */
    private void addAttributesItems(ObjectRule parentObjectRule, List<XsAttribute> attributes) {
        if (CollectionUtil.isEmpty(attributes)) {
            return;
        }
        for (XsAttribute attribute : attributes) {
            addAttributeItem(parentObjectRule, attribute);
        }
    }

    /**
     * 增加属性项
     *
     * @param parentObjectRule
     * @param attribute
     */
    private void addAttributeItem(ObjectRule parentObjectRule, XsAttribute attribute) {
        String type = attribute.getType();
        String defaultValue = attribute.getDefault();
        String describe = attribute.getAnnotation();

        RuleInterface rule = null;
        if (type.indexOf("xs:") == 0) {
            if (type.equals(XMLConstants.XS_boolean)) {
                rule = new BooleanRule(parentObjectRule, attribute);
                rule.apply();
            } else if (type.equals(XMLConstants.XS_NMTOKENS)) {
                rule = new EnumArrayRule(parentObjectRule, attribute);
                rule.apply();
            } else if (type.equals(XMLConstants.XS_NMTOKEN)) {
                rule = new EnumRule(parentObjectRule, attribute);
                rule.apply();
            } else if (type.equals(XMLConstants.XS_string)
                    || type.equals(XMLConstants.XS_token)
                    || type.equals(XMLConstants.XS_NOTATION)
                    || type.equals(XMLConstants.XS_NCName)
                    || type.equals(XMLConstants.XS_QName)
                    || type.equals(XMLConstants.XS_base64Binary)
                    || type.equals(XMLConstants.XS_duration)
                    || type.equals(XMLConstants.XS_normalizedString)) {
                rule = new StringRule(parentObjectRule, attribute);
                rule.apply();
            } else if (type.equals(XMLConstants.XS_int)
                    || type.equals(XMLConstants.XS_unsignedInt)
                    || type.equals(XMLConstants.XS_integer)
                    || type.equals(XMLConstants.XS_negativeInteger)
                    || type.equals(XMLConstants.XS_nonNegativeInteger)
                    || type.equals(XMLConstants.XS_nonPositiveInteger)
                    || type.equals(XMLConstants.XS_positiveInteger)
                    || type.equals(XMLConstants.XS_unsignedShort)) {
                rule = new IntegerRule(parentObjectRule, attribute);
                rule.apply();
            } else if (type.equals(XMLConstants.XS_long)) {
                rule = new LongRule(parentObjectRule, attribute);
                rule.apply();
            } else if (type.equals(XMLConstants.XS_decimal)
                    || type.equals(XMLConstants.XS_double)
                    || type.equals(XMLConstants.XS_float)
                    || type.equals(XMLConstants.XS_unsignedFloat)) {
                rule = new NumberRule(parentObjectRule, attribute);
                rule.apply();
            } else {
                rule = new StringRule(parentObjectRule, attribute);
                rule.apply();
            }
        } else {
            addEnumDefinition(type);
            rule = new RefRule(parentObjectRule, type);
            rule.apply();
        }
        if (ObjectUtil.isNotNull(rule)) {
            parentObjectRule.putProperty(attribute.getName(), rule);
        }
    }

    /**
     * 增加枚举定义
     *
     * @param typeName
     */
    private void addEnumDefinition(String typeName) {
        XsSimpleType simpleType = xsdReader.getPublicSimpleType(typeName);
        EnumRule enumRule = new EnumRule(rootObjectRule, simpleType);
        rootObjectRule.addDefinition(typeName, enumRule);
    }

    /**
     * 增加Element属性
     *
     * @param parentObjectRule
     * @param element
     */
    private void addElement(ObjectRule parentObjectRule, XsElement element) {
        if (element.getName().equals("table")) {
            System.out.println("请检查XSD文件，数组元素不能为空");
        }
        String minOccurs = element.getMinOccurs();
        String maxOccurs = element.getMaxOccurs();
        int maxOccursInt = 0;
        if (StrUtil.isNotEmpty(maxOccurs)) {
            if (NumberUtil.isNumber(maxOccurs)) {
                maxOccursInt = Integer.parseInt(maxOccurs);
            } else if (maxOccurs.equals("unbounded")) {
                maxOccursInt = Integer.MAX_VALUE;
            }
        }
        String typeName = xsdReader.getTypeName(element);
        if (maxOccursInt > 1) {
            ObjectArrayRule arrayRule = new ObjectArrayRule(parentObjectRule, element);
            arrayRule.apply();
            ObjectRule innerObjectRule = arrayRule.getInnerObjectRule();
            if (StrUtil.isNotEmpty(typeName)) {
                List<XsElement> extensionInnerElementList = xsdReader.getExtensionInnerElement(element);
                addObjectDefinition(typeName, extensionInnerElementList);
            } else {
                addPropertyContent(innerObjectRule, element);
            }
            parentObjectRule.putProperty(element.getName(), arrayRule);
        } else {
            if (StrUtil.isNotEmpty(typeName)) {
                List<XsElement> extensionInnerElementList = xsdReader.getExtensionInnerElement(element);
                addObjectDefinition(typeName, extensionInnerElementList);
                RefRule newObjectRule = new RefRule(parentObjectRule, typeName);
                newObjectRule.apply();
                parentObjectRule.putProperty(element.getName(), newObjectRule);
            } else {
                ObjectRule newObjectRule = new ObjectRule(parentObjectRule, element);
                newObjectRule.apply();
                addPropertyContent(newObjectRule, element);
                parentObjectRule.putProperty(element.getName(), newObjectRule);
            }
        }
    }

    /**
     * 为规则增加内容
     *
     * @param objectRule
     * @param element
     */
    private void addPropertyContent(ObjectRule objectRule, XsElement element) {
        addAttributesItems(objectRule, xsdReader.getAttributes(element));
        List<XsElement> elements = xsdReader.getElements(element);
        for (XsElement xsElement : elements) {
            addElement(objectRule, xsElement);
        }
    }

    /**
     * 增加引用的对象定义
     *
     * @param typeName
     * @param extensionInnerElementList
     */
    private void addObjectDefinition(String typeName, List<XsElement> extensionInnerElementList) {
        if(rootObjectRule.existDefinition(typeName)){
            return;
        }
        XsComplexType complexType = xsdReader.getPublicComplexType(typeName);
        ObjectRule objectRule = new ObjectRule(rootObjectRule, complexType);
        objectRule.apply();
        rootObjectRule.addDefinition(typeName, objectRule);
        List<XsAttribute> attributes = xsdReader.getAttributes(complexType);
        addAttributesItems(objectRule, attributes);
        List<XsElement> elements = xsdReader.getElements(complexType);
        List<XsElement> allChildElements = new ArrayList<>();
        if (elements != null) {
            allChildElements.addAll(elements);
        }
        if (extensionInnerElementList != null) {
            allChildElements.addAll(extensionInnerElementList);
        }
        addElements(objectRule, allChildElements);
    }


    public void save(String jsonSchemaFile) {
        JSONObject jsonObject = rootObjectRule.getJsonObject();
        String jsonStr = jsonObject.toString(JSONWriter.Feature.PrettyFormat);
        System.out.println(jsonStr);
        FileUtil.writeString(jsonStr, jsonSchemaFile, CharsetUtil.UTF_8);
    }

    public static void main(String[] args) {
        String rootPath = "D:/SeaBlueWork/goldMelonTools/src";
        try {
            XsdFileToJsonSchema xsdFileToTemplate = new XsdFileToJsonSchema("D:/SeaBlueWork/goldMelonTools/src/summer/xsdReader/summer.xsd");
            xsdFileToTemplate.save("D:/SeaBlueWork/goldMelonTools/src/summer/xsdReader/summer.json");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        System.out.println("模型转换完成");
    }
}
